package text

import (
	"image"
	"image/draw"
)

// redraw should be directly invoked after user interactions (mouse or keyboard events)
// and after exported library calls. Don't call redraw from within a lower level function,
// even if it necessitate a redraw.
func (b *Buffer) redraw() {
	b.drawlock.Lock()
	defer b.drawlock.Unlock()

	b.updatetext()
	clearr := b.r
	clearr.Min.X -= margin.X
	draw.Draw(b.img, clearr, b.bgcol, image.ZP, draw.Src)
	b.drawsel(image.ZR)

	for row, line := range b.lines {
		if line.dirty {
			// The line has been added or modified since we started this redraw.
			// Let the next redraw handle it.
			continue
		}
		pt := image.Pt(b.r.Min.X, b.getypx(row))
		r := line.img.Rect.Add(pt)
		draw.Draw(b.img, r, line.img, image.ZP, draw.Over)
	}

	if b.Dot.selection == selNone {
		col, row := b.Dot.Left.X, b.Dot.Left.Y
		// subtract a pixel from x coordinate to match acme
		pt := image.Pt(b.getxpx(row, col)-1, b.getypx(row))
		draw.Draw(b.img, b.cursor.Rect.Add(pt), b.cursor, image.ZP, draw.Src)
	}

	if b.scrollbar.Dirty {
		sbimg := b.scrollbar.Draw()
		b.scrollbar.Dirty = false
		draw.Draw(b.img, sbimg.Rect.Add(b.img.Rect.Min), sbimg, image.ZP, draw.Src)
	}

	b.DrawChan <- b.img
}

// like redraw, but only redraws changes in the selection
func (b *Buffer) redrawsel(seldiff image.Rectangle) {
	b.drawlock.Lock()
	defer b.drawlock.Unlock()

	b.updatetext()
	clearr := seldiff
	clearr.Min.X -= margin.X
	draw.Draw(b.img, clearr, b.bgcol, image.ZP, draw.Src)
	b.drawsel(seldiff) // TODO no need to redraw the whole selection

	for row, line := range b.lines {
		pt := image.Pt(b.r.Min.X, b.getypx(row))
		r := line.img.Rect.Add(pt)
		draw.Draw(b.img, r, line.img, image.ZP, draw.Over)
	}

	//dirtyr := b.r.Intersect(seldiff)
	//textr := dirtyr.Sub(b.r.Min).Add(b.scrollpt)
	//draw.Draw(b.img, dirtyr, b.textimg, textr.Min, draw.Over)

	if b.scrollbar.Dirty {
		sbimg := b.scrollbar.Draw()
		b.scrollbar.Dirty = false
		draw.Draw(b.img, sbimg.Rect.Add(b.img.Rect.Min), sbimg, image.ZP, draw.Src)
	}

	b.DrawChan <- b.img
}

func (b *Buffer) updatetext() {
	for _, line := range b.lines {
		if line.dirty {
			line.dirty = false
			b.drawline(line)
		}
	}
}

func (b *Buffer) drawsel(seldiff image.Rectangle) {
	if b.Dot.selection != selNone {
		row1, row2 := b.Dot.Left.Y, b.Dot.Right.Y
		col1, col2 := b.Dot.Left.X, b.Dot.Right.X
		x1, x2 := b.getxpx(row1, col1), b.getxpx(row2, col2)
		minx, maxx := b.r.Min.X, b.r.Max.X
		if row1 == row2 {
			b.drawselrect(seldiff, row1, x1, x2)
		} else if row2 > row1 {
			b.drawselrect(seldiff, row1, x1, maxx)
			for i := row1 + 1; i < row2; i++ {
				b.drawselrect(seldiff, i, minx, maxx)
			}
			b.drawselrect(seldiff, row2, minx, x2)
		}
	}
}

func (b *Buffer) drawselrect(seldiff image.Rectangle, row, x1, x2 int) {
	min := image.Pt(x1, b.getypx(row))
	max := image.Pt(x2, b.getypx(row+1))
	r := image.Rectangle{min, max}

	// zero seldiff means redraw everything
	if seldiff != image.ZR {
		r = r.Intersect(seldiff)
		if r == image.ZR {
			return
		}
	}
	draw.Draw(b.img, r, b.selcol, image.ZP, draw.Src)
}

func (b *Buffer) scroll(lines int) {
	top := 0
	bottom := b.font.Height * (len(b.lines) - 1)
	jump := b.font.Height * lines
	if lines < 0 {
		if b.scrollpt.Y+jump > top {
			b.scrollpt.Y += jump
		} else if b.scrollpt.Y != top {
			b.scrollpt.Y = top
		}
	} else {
		if b.scrollpt.Y+jump < bottom {
			b.scrollpt.Y += jump
		} else if b.scrollpt.Y != bottom {
			b.scrollpt.Y = bottom
		}
	}
	b.scrollbar.Dirty = true
}

func (b *Buffer) scrollto(pos int) {
	pospx := b.font.Height * pos
	bottom := b.font.Height * len(b.lines)
	if pospx >= 0 && pospx < bottom {
		b.scrollpt.Y = pospx
	}
	b.scrollbar.Dirty = true
}

// returns x (pixels) for a given row and col
func (b *Buffer) getxpx(row, col int) int {
	l := b.lines[row]
	if col >= len(l.px) {
		return l.px[len(l.px)-1] + b.r.Min.X
	}
	return l.px[col] + b.r.Min.X
}

// returns y (pixels) for a given row, compensating for scrollpt
func (b *Buffer) getypx(row int) int {
	return b.r.Min.Y + row*b.font.Height - b.scrollpt.Y
}

func (b *Buffer) drawline(l *line) {
	if l.img == nil {
		l.img = image.NewRGBA(image.Rect(0, 0, b.r.Dx(), b.font.Height))
	}
	draw.Draw(l.img, l.img.Rect, image.Transparent, image.ZP, draw.Src)
	l.px, _ = b.font.Draw(l.img, string(l.s), image.ZP, -1)
}
